module nade.font;

import derelict.sdl.sdl;
import derelict.opengl.gl;

import std.stdio;
import std.string;
import std.format;
import std.stream;
import std.math;

import nade.color;


enum TextMode {
	TOP_LEFT,
	TOP,
	TOP_RIGHT,
	LEFT,
	CENTER,
	RIGHT,
	BOTTOM_LEFT,
	BOTTOM,
	BOTTOM_RIGHT,
}


struct Glyph {
	float	x1 = 0, y1 = 0;
	float	x2 = 0, y2 = 0;
	float	w = 0;
}


Font font;


class Font {
	this(string path)
	{
		auto src = SDL_LoadBMP(cast(char*)toStringz(path ~ ".bmp"));
		auto tsrc = new std.stdio.File(path ~ ".txt", "rt");

		const yellow = SDL_MapRGB(src.format, 0xff, 0xff, 0x00);
		const magenta = SDL_MapRGB(src.format, 0xff, 0x00, 0xff);
		const transparent = SDL_MapRGB(src.format, 0xff, 0x00, 0xff);

		int x = 0;
		while(!tsrc.eof)
		{
			char[1] cs;
			tsrc.rawRead(cs);
			char c = cs[0];

			while(getPixel(src, x, 0) == yellow) ++x;

			int w = 1;
			while(getPixel(src, x + w, 0) != yellow  &&  (x + w) < src.w) ++w;

			_glyphs[c] = Glyph(x, 0, x + w, src.h, w/cast(real)src.h);

			x += w;
		}

		int next_p2(int a)
		{
			int result = 1;
			while(result < a) result <<= 1;
			return result;
		}
		int pw = next_p2(src.w);
		int ph = next_p2(src.h);

		foreach(ref glyph; _glyphs)
		{
			glyph.x1 /= pw;
			glyph.y1 /= ph;
			glyph.x2 /= pw;
			glyph.y2 /= ph;
		}


		ubyte[] data;
		data.length = pw*ph*4;

		for(x = 0;  x < src.w;  ++x)
		{
			for(int y = 0;  y < src.h; ++y)
			{
				const dp = (x + y*pw)*4;

				const p = getPixel(src, x, src.h - y - 1);
				if(p == yellow  ||  p == magenta)
				{
					data[dp] = 0x00;
					data[dp + 1] = 0x00;
					data[dp + 2] = 0x00;
					data[dp + 3] = 0x00;
				}
				else
				{
					SDL_GetRGB(p, src.format, &data[dp], &data[dp + 1], &data[dp + 2]);
					data[dp + 3] = 0xff;
				}
			}
		}

		glGenTextures(1, &_texture);
		glBindTexture(GL_TEXTURE_2D, _texture);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pw, ph, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.ptr);
	}

	void print(real x, real y, string text, real size, const Color color1, const Color color2)
	{
		glEnable(GL_TEXTURE_2D);

		glPushMatrix();
		glTranslatef(x, y, 0);
		glBindTexture(GL_TEXTURE_2D, _texture);
		foreach(k, c; text)
		{
			const g = _glyphs[c];
			const rfactor = (text.length > 1) ? k/(cast(real)text.length - 1) : 0.5;
			const factor = rfactor*rfactor;
			const omf = 1 - factor;

			color1.blended(color2, factor).render();
			glBegin(GL_POLYGON);
			glTexCoord2f(g.x1, g.y1); glVertex2f(0, 0);
			glTexCoord2f(g.x2, g.y1); glVertex2f(size*g.w, 0);
			glTexCoord2f(g.x2, g.y2); glVertex2f(size*g.w, size);
			glTexCoord2f(g.x1, g.y2); glVertex2f(0, size);
			glEnd();

			glTranslatef(size*g.w*1.05, 0, 0);
		}
		glPopMatrix();

		glDisable(GL_TEXTURE_2D);
	}
	void print(real x, real y, string text, real size, const Color color = Color.WHITE)
	{
		print(x, y, text, size, color, color);
	}

	void printCenter(real x, real y, string text, real size, const Color color1, const Color color2)
	{
		print(x - textWidth(text, size)*0.5, y, text, size, color1, color2);
	}
	void printCenter(real x, real y, string text, real size, const Color color = Color.WHITE)
	{
		printCenter(x, y, text, size, color, color);
	}

	void printRight(real x, real y, string text, real size, const Color color1, const Color color2)
	{
		print(x - textWidth(text, size), y, text, size, color1, color2);
	}
	void printRight(real x, real y, string text, real size, const Color color = Color.WHITE)
	{
		printRight(x, y, text, size, color, color);
	}

	void printFixed(real x, real y, string text, real width, real height, const Color color1, const Color color2)
	{
		glEnable(GL_TEXTURE_2D);

		glPushMatrix();
		glTranslatef(x, y, 0);
		glBindTexture(GL_TEXTURE_2D, _texture);
		foreach(k, c; text)
		{
			const g = _glyphs[c];
			const rfactor = (text.length > 1) ? k/(cast(real)text.length - 1) : 0.5;
			const factor = rfactor*rfactor;
			const omf = 1 - factor;
			const offset = (width - height*g.w)/2;

			color1.blended(color2, factor).render();
			glBegin(GL_POLYGON);
			glTexCoord2f(g.x1, g.y1); glVertex2f(offset, 0);
			glTexCoord2f(g.x2, g.y1); glVertex2f(offset + height*g.w, 0);
			glTexCoord2f(g.x2, g.y2); glVertex2f(offset + height*g.w, height);
			glTexCoord2f(g.x1, g.y2); glVertex2f(offset, height);
			glEnd();

			glTranslatef(width*1.05, 0, 0);
		}
		glPopMatrix();

		glDisable(GL_TEXTURE_2D);
	}
	void printFixed(real x, real y, string text, real width, real height, const Color color = Color.WHITE)
	{
		printFixed(x, y, text, width, height, color, color);
	}

	void printFixedCenter(real x, real y, string text, real width, real height, const Color color1, const Color color2)
	{
		printFixed(x - width*1.05*0.5, y, text, width, height, color1, color2);
	}
	void printFixedCenter(real x, real y, string text, real width, real height, const Color color = Color.WHITE)
	{
		printFixedCenter(x, y, text, width, height, color, color);
	}

	void printFixedRight(real x, real y, string text, real width, real height, const Color color1, const Color color2)
	{
		printFixed(x - width*1.05, y, text, width, height, color1, color2);
	}
	void printFixedRight(real x, real y, string text, real width, real height, const Color color = Color.WHITE)
	{
		printFixedRight(x, y, text, width, height, color, color);
	}

	real textWidth(string text, real size)
	{
		real w = 0;
		foreach(c; text)
		{
			const g = _glyphs[c];
			w += size*g.w*1.05;
		}

		return w;
	}

	private uint getPixel(SDL_Surface* surface, int x, int y)
	{
		int bpp = surface.format.BytesPerPixel;
		ubyte *p = cast(ubyte*)surface.pixels + y*surface.pitch + x*bpp;

		switch(bpp)
		{
			case 1: return *p;
			case 2: return *cast(ushort*)p;
			case 3:
			{
				if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
					return (p[0] << 16) | (p[1] << 8) | p[2];
				else
					return p[0] | (p[1] << 8) | (p[2] << 16);
			}
			case 4: return *cast(uint*)p;
			default:
		}

		return 0;
	}

	private {
		GLuint			_texture;
		Glyph[256]		_glyphs;
	}
}
